feat(cli): add openui-cloud template (cloud auth + telemetry)#673
Open
Aditya-thesys wants to merge 99 commits into
Open
feat(cli): add openui-cloud template (cloud auth + telemetry)#673Aditya-thesys wants to merge 99 commits into
Aditya-thesys wants to merge 99 commits into
Conversation
…istries + hooks refactor: rename Artifact API to DetailedView across react-headless and react-ui
panel via defineAppRenderer (react-headless). Renderers match by toolName (literal string or RegExp), captured at ChatProvider mount with first-wins on duplicates and dev-mode warnings on ambiguity. ToolMessageRenderer (react-ui) dispatches each tool message to its matching renderer or falls back to the default ToolResult. Wired into Shell, CopilotShell, and BottomTray Thread components. Args and response are passed raw to parsers; isStreaming is reserved (always false) until streaming protocol lands.
… migration
Map tool-call results to custom inline preview + detailed-view side panel
via renderers, and migrate openui-artifact-demo to demonstrate.
react-headless:
- defineAppRenderer / defineArtifactRenderer factories tag the config with
`kind` so a matched renderer registers in the apps or artifacts
ThreadContext slice
- AppRenderersContext + useAppRenderer for toolName lookup at mount
(literal Map + regex array; first-wins; dev-mode duplicate + ambiguity
warnings)
- processStreamedMessage handles TOOL_CALL_RESULT: creates a ToolMessage
via createMessage (signature broadened from AssistantMessage to Message)
- TEXT_MESSAGE_START is now a no-op — the previous delete+recreate broke
ordering when tool messages had already been appended; persistence
layers should map ids on save instead. Drop the unused deleteMessage
callback from processStreamedMessage's interface.
react-ui:
- ToolMessageRenderer dispatches matched tool messages; RendererInstance
runs parser → meta → register/unregister (routed by `kind`) and renders
preview inline + actual via DetailedViewPanel
- GenUIAssistantMessage renders matched tool messages outside
BehindTheScenes so the inline preview appears in the chat surface
- withChatProvider forwards `appRenderers` to ChatProvider (was missing
from the prop allowlist)
examples/openui-artifact-demo:
- Replace ArtifactCodeBlock openui-lang component with a `create_code_block`
tool + codeBlockRenderer (defineArtifactRenderer)
- enrichedArgsAdapter splits the backend's {_request, _response} envelope
into standard TOOL_CALL_ARGS + TOOL_CALL_RESULT events so processStreamed
Message and the bridge see the same shape as a standard agentic flow
- Override chat library preamble so the LLM is allowed to mix openui-lang
with tool calls
- Add create_code_block tool definition (declarative; returns {ok:true})
- "Workspace" heading with collapse/expand toggle
- Apps section reads useAppList(); artifacts section reads useArtifactList()
- Each item is the latest version per id; clicking activates the matching
DetailedView via setActiveDetailedView("${id}:${version}")
- Active item highlighted; empty states per section + overall hint
- Auto-collapses when a DetailedView opens (focus on the view) and
auto-expands when it closes; manual toggles between transitions are
preserved
- Hidden on mobile layout (desktop only for v1)
- Uses existing cssUtils tokens (spacing, typography, colors, radius)
- Wired into FullScreen via ComposedStandalone
- ShellStore gains isWorkspaceOpen / setIsWorkspaceOpen
handling
AppRenderers now fire on tool calls whose args are still streaming, before the
tool result is paired in. Same React instance persists across the
streaming → completed transition (keyed by toolCall.id), so renderers can
swap between partial and final UI without remounting.
- react-ui: ToolMessageRenderer accepts ToolMessage|null and signals
isStreaming when null; RendererInstance accepts an isStreaming prop and
propagates to controls + meta ctx; GenUIAssistantMessage dispatches every
tool call (paired or not).
- react-headless: openAIResponsesAdapter handles function_call_output items
in response.output_item.added → yields TOOL_CALL_RESULT. Backends that
inject synthetic events for server-side tool execution can now surface
tool results to the SDK store (OpenAI itself silently absorbs
function_call_output items into the conversation without echoing them).
- AppRendererControls.isStreaming JSDoc rewritten — was "always false in
v1; reserved for streaming protocol", now describes actual behavior.
AppRendererConfig.parser JSDoc notes args may be partial JSON and response
may be null during streaming. AppRendererConfig.meta documents the
ctx.isStreaming parameter.
…es APIs
behind the SDK's AG-UI wire shape.
- Persistent chat: thread storage via OpenAI Conversations API with a JSON
file index at .data/threads.json (Conversations API has no list method)
- Hosted LLM: route streams from openai.responses.create with
conversation: threadId so OpenAI auto-persists user input + responses
- Tool execution: manual loop (Responses API has no runTools helper),
capped at MAX_TOOL_TURNS, with a synthetic response.output_item.added
event injected after each tool execution so the SDK surfaces results live
- Streaming artifact: create_code_artifact tool with strict mode + property
order (language → title → code) and a partial-JSON parser, rendered via
defineArtifactRenderer using controls.isStreaming
- Frontend wires openAIResponsesAdapter + openAIConversationMessageFormat;
processMessage sends only the latest message since OpenAI holds history
Collapse the legacy flat-props ChatProvider config behind two
adapter interfaces:
- ChatStorage (thread, pinning?, share?) drives the storage channel
- ChatLLM (send, streamProtocol) drives the LLM channel
Drops apiUrl, threadApiUrl, processMessage, loadThread, fetchThreadList,
createThread/deleteThread/updateThread props, messageFormat, and the
top-level streamProtocol prop. Their behavior is now configured per
adapter; the bundled fetchLLM factory captures the previous default
fetch + messageFormat path.
react-headless:
- Add src/adapters: ChatStorage / ChatLLM / Thread/Pinning/ShareStorage
types, fetchLLM factory, internal _defaultStorage (in-memory, not
exported) used when ChatProvider has no storage prop
- Rewrite createChatStore to consume { storage, llm }; thread CRUD now
delegates to storage.thread.*, message send to llm.send
- ChatProvider props collapse to { storage?, llm, appRenderers, children }
- Guard process.env reads with `typeof process !== "undefined"` so the
provider/registry/detailed-view store work in non-Node runtimes
- Migrate createChatStore / threadContextSwitch / detailedViewThreadSwitch
tests behind a shared makeStore() helper; drop the apiUrl /
threadApiUrl / messageFormat / ephemeral-thread describe blocks (those
paths now live in the fetchLLM adapter)
react-ui:
- withChatProvider HOC: replace prop-key filtering with a destructure
over the new shape
- Add shared __test-helpers/mockChat.ts (makeMockStorage, makeMockLLM,
mockSSEResponse) used by every story
- Migrate Shell, BottomTray, CopilotShell, and OpenUIChat stories to
<ChatProvider storage={...} llm={...}>
examples/openui-artifact-demo: import fetchLLM from react-headless.
# Conflicts: # packages/react-ui/src/components/CopilotShell/Thread.tsx # packages/react-ui/src/components/Shell/Sidebar.tsx # packages/react-ui/src/components/Shell/thread.scss # packages/react-ui/src/components/index.scss # pnpm-lock.yaml
REST ChatStorage hitting the exact endpoints the removed threadApiUrl prop used (/get · /create · /get/:id · /update/:id · /delete/:id), so consumers migrate by swapping one prop for one factory call. 8 tests cover URL/method/body/headers per method, messageFormat round-trip, and res.ok error surfacing. - store._nextCursor: `any` → `string | undefined`, consistent with the ThreadStorage interface's `string` cursor. - react-ui re-exports the adapter construction surface (fetchLLM, restStorage, ChatStorage, ChatLLM, ThreadStorage, Pinning/Share types, FetchLLMOptions, RestStorageOptions) so AgentInterface consumers don't need to reach into @openuidev/react-headless.
# Conflicts: # pnpm-lock.yaml
renderers, per-thread Workspace rail
Storage (react-headless):
- ArtifactStorage channel on ChatStorage.artifact — list (server-side
name/type filters, string-cursor pagination) · get · update({id,
content}) for editable artifacts. ArtifactSummary carries a required
threadId (drives "go to thread"); Artifact.content must match the
tool-response shape so renderers parse both sources identically.
- ArtifactCategory config ({ name, filter: { type[] } }) on ChatProvider;
useArtifactStorage() + useArtifactCategories() hooks.
- BREAKING: PinningStorage / ShareStorage / ShareTarget removed (dead
interfaces, zero consumers).
Renderer unification (BREAKING):
- defineAppRenderer, kind, AppRendererKind, AppEntry, useAppList deleted.
- defineArtifactRenderer gains type (links renderer ↔ categories ↔ stored
artifacts) and toolName: string | string[] (RegExp matching removed);
parser + meta merged into parser(raw, {isStreaming}) → {props, meta} |
null. Registry is a flat byToolName map + byType index;
useArtifactRenderer replaces useAppRenderer.
- ThreadContext unified into a single artifacts registry; entries carry
type; useArtifactList(filter?: { type?: string[] }).
- ChatProvider prop renamed appRenderers → artifactRenderers (+ new
artifactCategories).
Artifact browser + Workspace (react-ui / AgentInterface):
- AgentInterface.ArtifactNav — one SidebarItem per category (single
"Artifacts" when uncategorized), auto-included in the default sidebar
when storage.artifact is configured.
- Reserved nav paths matched before user Routes:
artifacts/{category} → searchable list (debounced title search +
category type filter, cursor "Load more");
artifacts/{category}/{id} → full-page artifact view (Back, Go to
thread, renderer actual via byType lookup, no DetailedView).
- AgentInterface.Workspace — per-thread right rail listing registered
artifacts grouped by category; auto-shows on first artifact, hidden on
Route/browser pages and mobile; item click opens the in-thread
DetailedView (rail auto-collapses). Modes A/C.
- WorkspaceSidebar (Shell) sections now category-driven.
- react-ui re-exports the artifact surface (defineArtifactRenderer,
ArtifactStorage/Category/Summary types).
Consumers migrated: openui-responses-chat page.tsx (was still on
pre-adapter flat props; now restStorage + custom llm.send),
codeBlockRenderer + codeArtifactRenderer to merged parser + type,
withChatProvider, both tool-renderer copies, mockChat/makeStore.
Tests: registry/threadContext suites rewritten for the unified model
(83 passing). Stories: ArtifactBrowser, ArtifactBrowserUncategorized,
WithWorkspace (scripted tool-call SSE), WorkspaceCustomChildren — all
Storybook-verified.
Example (examples/openui-cloud): Next.js app showing OpenUI Cloud's two-plane integration — server-side generation with the master key, browser-direct reads/edits with a short-lived frontend token — plus artifact renderers over the @openuidev/thesys viewers and a one-call ChatStorage (openuiCloud). openuiCloud's apiBaseUrl is optional and defaults to https://api.thesys.dev. The vendored @openuidev/thesys tarball is gitignored (obtain via the registry, not this repo). SDK: - react-headless: upsert tool messages by toolCallId (artifact morph), SSE cross-read buffering + artifact_call.delta accumulation, and multi-tool reload grouping in fromItems. - react-ui: artifact viewId migration, inline-sentinel message format, scoped ShellStoreProvider under AgentInterface, and content-header round-trip in GenUIAssistantMessage.
…2e fixes - react-headless: openai-responses adapter delivers the accumulated sentinel carrier directly on artifact_call.delta (no JSON re-wrap). - react-ui: rename contentParser -> sentinelParser (unified ]]>openui: family, adds parseArtifactSentinel); RendererInstance now follows an edit's NEW version in an already-open detailed view (cross-instance activeDetailedView migration); update sentinelParser importers. - example: parseArtifact parses the sentinel carrier; artifactStorage.get returns the bare program (fixes the global artifact browser 'could not parse'); next.config aliases lucide dynamic-icon subpaths + the cross-repo @openuidev/thesys-server entry for Turbopack.
…penui into abhishek/openui-chat
# Conflicts: # pnpm-lock.yaml
… provider - Replace invalid `typography(primary, default)` (no such category — it compiled to no font/letter-spacing) with `typography(body, default)` for chat message text, user bubble, and artifact browser/view loading/error/ empty text. - SidebarHeader rendered `<img src="">` when logoUrl was empty (browsers resolve that to the page URL → spurious request + broken-image icon); skip the <img> when logoUrl is empty. - Hoist a single `Tooltip.Provider` into Container and drop the per-instance providers in AgentInterfaceTooltip/SidebarTooltip so Radix's hover-delay / skip-delay behavior is shared across all tooltips. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The ResizableSeparator between the chat and detailed-view panels was mouse-drag only with no accessibility. Implement the WAI-ARIA window- splitter pattern: - role="separator", aria-orientation, tabIndex=0, aria-label, and aria-controls referencing both the chat and detailed-view panels; live aria-valuenow/min/max/valuetext as % of the container. - Keyboard: Left/Right resize by 16px (Shift = 64px), Home/End snap to min/max; clamped to [420px, 80% of container]. - :focus-visible outline + visible handle. The hook now tracks the chat-panel width in a ref (kept in sync with style.width) so keyboard steps and ARIA read a stable value rather than measuring mid-CSS-transition, and resets it when the detailed view closes so the separator's mount-time ARIA is correct on reopen. Added zero-width-container guards so resizing can't collapse the panel to 0px. Verified in Storybook: arrows/Shift/Home/End, min/max clamping, ARIA values, focus ring, mouse-drag regression, and reopen-after-resize. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…g-ui/core to 0.0.53
The user-message renderer only handled text and binary parts that had a
`url` (rendered as a raw <img>), silently dropping base64 (`data`)
attachments, non-image mime types, and filenames.
- Bump @openuidev/react-headless's @ag-ui/core ^0.0.45 -> ^0.0.53. This is
additive for content (InputContent gains first-class image|audio|video|
document parts alongside text|binary) and non-breaking for the streaming
protocol (no EventType members removed; react-headless typecheck + build
+ 83 tests pass).
- Extract UserMessageContent into its own module and handle the full
InputContent union exhaustively (a `default: never` guard surfaces future
variants at compile time — it's what forced the new branches after the bump):
- image/audio/video -> media elements; document/other -> downloadable file chip.
- binary routed by mimeType.
- sources (url + base64 `data`) vetted against a scheme/mime allowlist
(reject javascript:/blob:/etc.); unusable or failed-to-load media degrade
to a file chip instead of being dropped or showing a broken glyph.
- filenames surfaced; real alt/aria-label; loading="lazy".
Verified in Storybook: url/data images, audio, pdf/zip chips, and a
javascript: source were all handled correctly (no unsafe src reaches the DOM).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Navigating to a reserved `artifacts/{category}` path whose category isn't
configured (stale/renamed path, hand-edited or bookmarked URL) previously
fell through to an unfiltered `storage.list()` and rendered EVERY artifact
under the bogus category name — looking like a real category that contains
everything.
- Detect the case (`categoryName` set but not in `artifactCategories`) and
render a "No category named X" state with a "View all artifacts" button;
skip the storage query entirely.
- Remove the dead, required `title` prop from `ArtifactPreviewIllustration`
(the component is aria-hidden and never rendered it) and its call sites,
clearing a pre-existing unused-var lint error on this file.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tup reproducible - page.tsx: use the useOpenuiCloudStorage() hook in-component (the module-scope openuiCloud() factory was replaced upstream in @openuidev/thesys on ap-server). - README: add a "Local dependency wiring" section so teammates can reproduce the gitignored vendor setup (build genui-sdk c1 + c1-server on ap-server, pack/copy into vendor/, pnpm install --force); refresh the hook + renderer references. - Add .env.example template + a .gitignore exception (!.env.example) so it ships while .env.local stays ignored. - next.config: turbopack alias @openuidev/thesys-server -> vendored build, plus lucide dynamic-icon stubs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address the branch review: - pnpm-lock.yaml: restore to base so this branch carries ZERO lockfile change. The fastapi workspace:* switch's `pnpm install` had regenerated the whole lockfile (tree-wide babel drift + openui-cloud's externally-linked deps), and the base lockfile is itself pre-existingly stale (missing openui-cloud / openui-responses-chat importers — frozen-install already fails on base). The lockfile reconciliation (fastapi's workspace links + the pre-existing drift) belongs in a dedicated, clean `pnpm install` PR, not entangled with the shell retirement. - AgentInterface/dependencies.ts: stale `"Shell"` self-ref → `"AgentInterface"` (Shell was deleted; this dead-code manifest still named it). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JsBw2b6YnH9J5GAhkDyonF
… orphaned assets) A repo-wide sweep after the shell deletion found 10 leftover references to the removed FullScreen/Copilot/BottomTray shells (no live imports — these are dead CSS/docs/assets): - docs chat-modal.css: dead `.openui-shell-container` / `.openui-shell-sidebar-container` overrides — the demo now renders AgentInterface (`openui-agent-*`), so the modal sizing + sidebar-hide had silently broken; retargeted to the agent classes. - AgentInterface/README.md: dropped two stale `Shell` *component* references (the shared ShellStore is intentionally kept). - OpenUIChat/assistantMessage.scss: retargeted the mobile / detailed-view ANCESTOR selectors from the deleted Shell container to AgentInterface's (`openui-agent-container--mobile`, `openui-agent-thread-container--detailed-view-active`) — restores the assistant-message mobile/detailed-view layout the extraction left inert. - Deleted 7 orphaned, unreferenced shell assets (copilot.png, fullscreen.png, fullscreen-dark.png, bottom-tray.png, bottom-tray.gif, architecture.svg, layouts.svg). react-ui builds; repo-wide re-sweep finds zero real shell remnants. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JsBw2b6YnH9J5GAhkDyonF
…ParsedArtifact interface. Update ToolActivityRenderer to prioritize `meta.type` for accurate artifact classification, ensuring reports and slides are correctly labeled. This change improves the clarity of artifact types in the browser and aligns with recent parser updates.
…r enhanced user experience - Added videos demonstrating artifact interactions and generative UI components in various contexts. - Included images showcasing conversation history, custom assistant messages, sidebar navigation, and welcome states. - Updated relevant documentation files to reflect these additions, improving clarity and engagement for users.
…erface Delete the FullScreen/Copilot/BottomTray shells from @openuidev/react-ui and migrate all workspace consumers to AgentInterface (zero react-headless change, zero lockfile change). Includes the AssistantMessageContainer extraction, docs retargeting, and the shell-remnant cleanup.
…een for now
The CLI template is decoupled from the workspace shell deletion: scaffolded
projects pin the PUBLISHED @openuidev/react-ui ("latest", which still ships
FullScreen), and templates are copied verbatim by build-templates.js (the CLI's
tsc excludes src/templates — they are never compiled here). So migrating the
template to AgentInterface was not required for the shell-retirement PR. Defer it
to the follow-up that migrates the other published-deps consumers (fastapi,
genui-sdk) once react-ui is republished without the shells.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JsBw2b6YnH9J5GAhkDyonF
… sections The AgentInterface workspace rail hardcoded an "all"/"artifacts"/"apps" tab set and magic-matched the "apps" tab on the category's display NAME (`category.name.toLowerCase() === "apps"`) instead of deriving from the consumer-supplied artifactCategories. Per "no top bar, no filtering for now": remove the whole tab layer — the WorkspaceTab union, WORKSPACE_TAB_ORDER, isAppsCategory, getAvailableWorkspaceTabs, the WorkspaceTabs component, the top bar, and the activeTab filter. The rail now renders one section per category that has entries (or a single Artifacts section), unfiltered. Supersedes the parked SHOW_WORKSPACE_TOP_BAR=false flag. The labels.tabs API and the tab SCSS are intentionally left in place (unused) for when category-driven tabs are added properly later. react-ui builds + tsc + tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JsBw2b6YnH9J5GAhkDyonF
The shell retirement (ff5416f) extracted only the ASSISTANT half of the deleted Shell's thread.scss into OpenUIChat/assistantMessage.scss. GenUIUserMessage — which AgentInterface renders for every user turn — still emits `.openui-shell-thread-message-user` and `…__content`, but their styling was left behind in the deleted file, so the user message lost its right-alignment (`justify-content: flex-end`) and its bubble (background, padding, radius). Restore both rules alongside the assistant block (same file is already forwarded by openUIChat.scss → components/index.scss, so no index change), with the mobile/detailed-view ANCESTOR selectors retargeted shell→agent to match the assistant extraction. Verified: class present in dist/styles/index.css and the user message renders as a right-aligned bubble in openui-cloud. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JsBw2b6YnH9J5GAhkDyonF
ToolActivityRenderer fed an errored tool-call's result string into the artifact parser as `response`; the parser can't read it and returns null, so the live preview (e.g. a streaming slide deck) was replaced by the fallback card — the artifact appeared to "disappear" before the model's retry. Retain the last successful parse (a ref updated whenever parse is non-null) and, on an error frame, render it instead of blanking to the fallback. Calls that error before producing any parse still fall through to the fallback card, and registration/meta still key off the live parse (an error never registers). Verified: react-ui tsc + 6 tests pass + build green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JsBw2b6YnH9J5GAhkDyonF
…rors" This reverts dd7c9ce. A failed tool call has no real artifact — it never registers or persists, so it correctly vanishes on refresh. The guard retained the failed call's in-flight preview live, creating a live-vs-refresh inconsistency (two decks live, one after reload). Desired behavior: a failed call shows no artifact, matching the persisted state. Restores the original fallback-on-null rendering. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JsBw2b6YnH9J5GAhkDyonF
The c1 artifact parser renders a preview from the streamed tool args (artifact_content), independent of the result — so a FAILED tool call still produced a non-null parse and showed a phantom artifact card that was never registered and vanished on refresh (a live-vs-reload inconsistency the user hit). The prior `parsed === null` guard never fired for this path. Render the fallback (raw tool card) when `activity.isError`, so the UI only shows artifacts that actually succeeded — consistent live and after reload. Streaming previews are unaffected (isError is only set once an error result lands). Verified: react-ui tsc + 6 tests + build; dist carries `isError || parsed === null`. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JsBw2b6YnH9J5GAhkDyonF
…ection The per-thread Workspace rail grouped registered artifacts into one section per configured artifactCategories entry. An artifact whose registered entry.type matched no category's filter.type rendered in no section and silently vanished from the rail (its inline chat preview still showed). entry.type is dynamic (meta.type ?? renderer.type), so a parser stamping an artifact's real kind can produce a type absent from every category filter. Append a fallback section for entries matching no configured category so a registered artifact is never dropped — mirroring the no-categories path that already lists everything. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JsBw2b6YnH9J5GAhkDyonF
Delete the openui-artifact-demo example app in its entirety. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JsBw2b6YnH9J5GAhkDyonF
# Conflicts: # docs/content/docs/openui-lang/examples/harnesses/pi-agent-harness.mdx # examples/harnesses/pi-agent-harness/src/app/page.tsx # pnpm-lock.yaml
…to cli-openui-cloud-template
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.